/*
 * Spring 2013 TCSS 558 - Applied Distributed Computing Institute of Technology,
 * UW Tacoma Written by Sean
 */

package util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * This class is to write any log message in a file. Every actual node logs its
 * activity (startup information including its ID number and the m for the
 * system, messages sent and received, values stored and retrieved, etc.) to a
 * log file with a reasonable name (e.g., "log-ID.txt" where "ID" is the node's
 * ID number).
 * 
 */
public final class LogWriter
{
  /**
   * Platform independent new line.
   */
  private static final String NEW_LINE = System.getProperty("line.separator");

  /**
   * Platform independent file separator.
   */
  private static final String SEPARATOR = System.getProperty("file.separator");

  /**
   * A constructor to prevent instantiation.
   */
  private LogWriter()
  {
    // do nothing
  }

  /**
   * Log startup information for a node. Should be called every time a node is
   * created.
   * 
   * @param the_node_id the id of the startup node
   * @param the_m_value the m value
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logStartup(final int the_node_id, final int the_m_value)
  {
    final Double total_nodes = Math.pow(2, the_m_value);
    final String log_msg =
        "Startup Information:" + NEW_LINE + "Node ID: " + the_node_id + NEW_LINE +
            "\"M\" value: " + the_m_value + NEW_LINE + "Total nodes: " +
            total_nodes.intValue();
    final boolean write_startup = writeToFile(the_node_id, log_msg);
    return write_startup && logNodeChannel(the_node_id, the_m_value);
  }

  /**
   * Log the nodes that are able to communicate with the node in question.
   * 
   * @param the_node_id the id of the startup node
   * @param the_m_value the m value
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logNodeChannel(final int the_node_id,
                                                    final int the_m_value)
  {
    String log_msg = "Nodes with open channels to this node: ";
    final Double total_nodes = Math.pow(2, the_m_value);
    for (int k = 0; k < the_m_value; k++)
    {
      Double value = the_node_id + (Math.pow(2, k) % total_nodes);
      if (value > total_nodes)
      {
        value = the_node_id - (Math.pow(2, k) % total_nodes);
      }
      log_msg += value.intValue() + ", ";
    }
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Log successor information for a node.
   * 
   * @param the_node_id the id of the startup node
   * @param the_successors a string of all successors
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logSuccessor(final int the_node_id,
                                                  final String the_successors)
  {
    final String successors = the_successors.trim();
    String log_msg = "Successors: " + the_successors;
    if ("".equals(successors))
    {
      log_msg = "Successors: None";
    }
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Log when/if the node safely leaves the ring.
   * 
   * @param the_node_id the id of the leaving node
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logLeaving(final int the_node_id)
  {
    final String log_msg = "Node number " + the_node_id + " has left safely.";
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Log if/when the node is discovered to have crashed.
   * 
   * @param the_node_id the id of the crashing node
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logCrashed(final int the_node_id)
  {
    final String log_msg = "Node number " + the_node_id + " has crashed.";
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Log messages sent.
   * 
   * @param the_node_id the id of the sending node
   * @param the_message the message sent
   * @param the_rec_node_id the id of the receiving node
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logMsgSent(final int the_node_id,
                                                final String the_message,
                                                final int the_rec_node_id)
  {
    final String log_msg =
        "Sent message " + the_message + " to node number " + the_rec_node_id;
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Log messages received.
   * 
   * @param the_node_id the id of the receiving node
   * @param the_message the message received
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logMsgReceived(final int the_node_id,
                                                    final String the_message)
  {
    final String log_msg = "Received message " + the_message;
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Log values stored in the node.
   * 
   * @param the_node_id the id of the node storing value
   * @param the_stored_value the value stored
   * @return true if written successfully, false otherwise
   */
  public static boolean logValueStored(final int the_node_id, final int the_stored_value)
  {
    final String log_msg = "Logged value " + the_stored_value;
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Log values retrieved from the node.
   * 
   * @param the_node_id the id of the node which stored value is being retrieved
   * @param the_ret_value the value of the retrieved value
   * @return true if written successfully, false otherwise
   */
  public static synchronized boolean logValuesRet(final int the_node_id,
                                                  final int the_ret_value)
  {
    final String log_msg = "Value " + the_ret_value + " was retrieved from node.";
    return writeToFile(the_node_id, log_msg);
  }

  /**
   * Write log data to file.
   * 
   * @param the_node_id the id of the nodes log file to write to
   * @param the_log_data the data to write to file
   * @return true if written successfully, false otherwise
   */
  private static synchronized boolean writeToFile(final int the_node_id,
                                                  final String the_log_data)
  {
    final SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss.SSS", Locale.US);
    boolean written = true;
    try
    {
      // Make new directory
      final File dir = new File("Logs");
      dir.mkdir();

      // Time stamp log data.
      final String data = date.format(new Date()) + ": " + the_log_data + NEW_LINE;
      final File file =
          new File(dir.getAbsolutePath() + SEPARATOR + "log-" + the_node_id + ".txt");

      // Check if file exists, if not create new one. Assume unique node ID,
      // therefore unique filename.
      if (!file.exists())
      {
        file.createNewFile();
      }

      // Appends to file.
      final FileWriter fw = new FileWriter(file.getAbsoluteFile(), true);
      final BufferedWriter bw = new BufferedWriter(fw);
      bw.write(data);
      bw.close(); // Close file.

    }
    catch (final IOException e)
    {
      written = false;
      Log.err("Error writing to file.");
      e.printStackTrace();
    }
    return written;
  }
}
